using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Text;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.transaction
{


	using HeuristicMixedException = javax.transaction.HeuristicMixedException;
	using HeuristicRollbackException = javax.transaction.HeuristicRollbackException;
	using RollbackException = javax.transaction.RollbackException;
	using Status = javax.transaction.Status;
	using Synchronization = javax.transaction.Synchronization;
	using SystemException = javax.transaction.SystemException;
	using Transaction = javax.transaction.Transaction;
	using XAException = javax.transaction.xa.XAException;
	using XAResource = javax.transaction.xa.XAResource;
	using Xid = javax.transaction.xa.Xid;

	internal class TransactionImpl : Transaction
	{
		private static Logger log = Logger.getLogger(typeof(TransactionImpl).Name);

		private const int RS_ENLISTED = 0;
		private const int RS_SUSPENDED = 1;
		private const int RS_DELISTED = 2;
		private const int RS_READONLY = 3; // set in prepare

		private readonly sbyte[] globalId;
		private int status = Status.STATUS_ACTIVE;
		private bool active = true;
		private bool globalStartRecordWritten = false;

		private readonly LinkedList<ResourceElement> resourceList = new LinkedList<ResourceElement>();
		private List<Synchronization> syncHooks = new List<Synchronization>();

		private readonly int eventIdentifier;

		private readonly TxManager txManager;

		internal TransactionImpl(TxManager txManager)
		{
			this.txManager = txManager;
			globalId = XidImpl.getNewGlobalId();
			eventIdentifier = txManager.getNextEventIdentifier();
		}

		internal virtual int getEventIdentifier()
		{
			return eventIdentifier;
		}

		internal virtual sbyte[] getGlobalId()
		{
			return globalId;
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public override string ToString()
		{
			StringBuilder txString = new StringBuilder("Transaction[Status=" + txManager.getTxStatusAsString(status) + ",ResourceList=");
			LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
			while (itr.MoveNext())
			{
				txString.Append(itr.Current.ToString());
				if (itr.MoveNext())
				{
					txString.Append(",");
				}
			}
			return txString.ToString();
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, IllegalStateException, SystemException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void commit()
		{
		// make sure tx not suspended
			txManager.commit();
		}

		internal virtual bool isGlobalStartRecordWritten()
		{
			return globalStartRecordWritten;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void rollback() throws IllegalStateException, SystemException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void rollback()
		{
		// make sure tx not suspended
			txManager.rollback();
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual bool enlistResource(XAResource xaRes)
		{
			if (xaRes == null)
			{
				throw new IllegalArgumentException("Null xa resource");
			}
			if (status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING)
			{
				try
				{
					if (resourceList.Count == 0)
					{
						if (!globalStartRecordWritten)
						{
							txManager.writeStartRecord(globalId);
							globalStartRecordWritten = true;
						}
					// 
						sbyte[] branchId = txManager.getBranchId(xaRes);
						Xid xid = new XidImpl(globalId, branchId);
						resourceList.AddLast(new ResourceElement(xid, xaRes));
						xaRes.Start(xid, XAResource.TMNOFLAGS);
						try
						{
							txManager.getTxLog().addBranch(globalId, branchId);
						}
						catch (IOException e)
						{
							e.printStackTrace();
							log.severe("Error writing transaction log");
							txManager.setTmNotOk();
							throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
						}
						return true;
					}
					Xid sameRmXid = null;
					LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
					while (itr.MoveNext())
					{
						ResourceElement re = itr.Current;
						if (sameRmXid == null && re.getResource().isSameRM(xaRes))
						{
							sameRmXid = re.getXid();
						}
						if (xaRes == re.getResource())
						{
							if (re.getStatus() == RS_SUSPENDED)
							{
								xaRes.Start(re.getXid(), XAResource.TMRESUME);
							}
							else
							{
							// either enlisted or delisted
							// is TMJOIN correct then?
								xaRes.Start(re.getXid(), XAResource.TMJOIN);
							}
							re.setStatus(RS_ENLISTED);
							return true;
						}
					}
					if (sameRmXid != null) // should we join?
					{
						resourceList.AddLast(new ResourceElement(sameRmXid, xaRes));
						xaRes.Start(sameRmXid, XAResource.TMJOIN);
					}
					else
				// new branch
					{
					// ResourceElement re = resourceList.getFirst();
						sbyte[] branchId = txManager.getBranchId(xaRes);
						Xid xid = new XidImpl(globalId, branchId);
						resourceList.AddLast(new ResourceElement(xid, xaRes));
						xaRes.Start(xid, XAResource.TMNOFLAGS);
						try
						{
							txManager.getTxLog().addBranch(globalId, branchId);
						}
						catch (IOException e)
						{
							e.printStackTrace();
							log.severe("Error writing transaction log");
							txManager.setTmNotOk();
							throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
						}
					}
					return true;
				}
				catch (XAException e)
				{
					e.printStackTrace();
					log.severe("Unable to enlist resource[" + xaRes + "]");
					status = Status.STATUS_MARKED_ROLLBACK;
					return false;
				}
			}
			else if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK || status == Status.STATUS_MARKED_ROLLBACK)
			{
				throw new RollbackException("Tx status is: " + txManager.getTxStatusAsString(status));
			}
			throw new IllegalStateException("Tx status is: " + txManager.getTxStatusAsString(status));
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual bool delistResource(XAResource xaRes, int flag)
		{
			if (xaRes == null)
			{
				throw new IllegalArgumentException("Null xa resource");
			}
			if (flag != XAResource.TMSUCCESS && flag != XAResource.TMSUSPEND && flag != XAResource.TMFAIL)
			{
				throw new IllegalArgumentException("Illegal flag: " + flag);
			}
			ResourceElement re = null;
			LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
			while (itr.MoveNext())
			{
				ResourceElement reMatch = itr.Current;
				if (reMatch.getResource() == xaRes)
				{
					re = reMatch;
					break;
				}
			}
			if (re == null)
			{
				return false;
			}
			if (status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK)
			{
				try
				{
					xaRes.end(re.getXid(), flag);
					if (flag == XAResource.TMSUSPEND || flag == XAResource.TMFAIL)
					{
						re.setStatus(RS_SUSPENDED);
					}
					else
					{
						re.setStatus(RS_DELISTED);
					}
					return true;
				}
				catch (XAException e)
				{
					e.printStackTrace();
					log.severe("Unable to delist resource[" + xaRes + "]");
					status = Status.STATUS_MARKED_ROLLBACK;
					return false;
				}
			}
			throw new IllegalStateException("Tx status is: " + txManager.getTxStatusAsString(status));
		}

	// TODO: figure out if this needs syncrhonization or make status volatile
		public virtual int getStatus() // throws SystemException
		{
			return status;
		}

		internal virtual void setStatus(int status)
		{
			this.status = status;
		}

		private bool beforeCompletionRunning = false;
		private List<Synchronization> syncHooksAdded = new List<Synchronization>();

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public synchronized void registerSynchronization(Synchronization s) throws RollbackException, IllegalStateException
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void registerSynchronization(Synchronization s)
		{
			if (s == null)
			{
				throw new IllegalArgumentException("Null parameter");
			}
			if (status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING || status == Status.STATUS_MARKED_ROLLBACK)
			{
				if (!beforeCompletionRunning)
				{
					syncHooks.Add(s);
				}
				else
				{
				// avoid CME if synchronization is added in before completion
					syncHooksAdded.Add(s);
				}
			}
			else if (status == Status.STATUS_ROLLING_BACK || status == Status.STATUS_ROLLEDBACK)
			{
				throw new RollbackException("Tx status is: " + txManager.getTxStatusAsString(status));
			}
			else
			{
				throw new IllegalStateException("Tx status is: " + txManager.getTxStatusAsString(status));
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void doBeforeCompletion()
		{
			beforeCompletionRunning = true;
			try
			{
				foreach (Synchronization s in syncHooks)
				{
					try
					{
						s.beforeCompletion();
					}
					catch (System.Exception t)
					{
						log.warning("Caught exception from tx syncronization[" + s + "] beforeCompletion()");
					}
				}
			// execute any hooks added since we entered doBeforeCompletion
				while (!syncHooksAdded.Count == 0)
				{
					List<Synchronization> addedHooks = syncHooksAdded;
					syncHooksAdded = new List<Synchronization>();
					foreach (Synchronization s in addedHooks)
					{
						s.beforeCompletion();
						syncHooks.Add(s);
					}
				}
			}
			finally
			{
				beforeCompletionRunning = false;
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void doAfterCompletion()
		{
			foreach (Synchronization s in syncHooks)
			{
				try
				{
					s.afterCompletion(status);
				}
				catch (System.Exception t)
				{
					log.warning("Caught exception from tx syncronization[" + s + "] afterCompletion()");
				}
			}
			syncHooks = null; // help gc
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: public void setRollbackOnly() throws IllegalStateException
		public virtual void setRollbackOnly()
		{
			if (status == Status.STATUS_ACTIVE || status == Status.STATUS_PREPARING || status == Status.STATUS_PREPARED || status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLING_BACK)
			{
				status = Status.STATUS_MARKED_ROLLBACK;
			}
			else
			{
				throw new IllegalStateException("Tx status is: " + txManager.getTxStatusAsString(status));
			}
		}

		public override bool Equals(object o)
		{
			if (!(o is TransactionImpl))
			{
				return false;
			}
			TransactionImpl other = (TransactionImpl) o;
			return this.eventIdentifier == other.eventIdentifier;
		}

		private volatile int hashCode = 0;

		public override int GetHashCode()
		{
			if (hashCode == 0)
			{
				hashCode = 3217 * eventIdentifier;
			}
			return hashCode;
		}

		internal virtual int getResourceCount()
		{
			return resourceList.Count;
		}

		private bool isOnePhase()
		{
			if (resourceList.Count == 0)
			{
				log.severe("Detected zero resources in resourceList");
				return true;
			}
		// check for more than one unique xid
			LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
			Xid xid = itr.Current.getXid();
			while (itr.MoveNext())
			{
				if (!xid.Equals(itr.Current.getXid()))
				{
					return false;
				}
			}
			return true;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: void doCommit() throws XAException, SystemException
		internal virtual void doCommit()
		{
			bool onePhase = isOnePhase();
			bool readOnly = true;
			if (!onePhase)
			{
			// prepare
				status = Status.STATUS_PREPARING;
				LinkedList<Xid> preparedXids = new LinkedList<Xid>();
				LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
				while (itr.MoveNext())
				{
					ResourceElement re = itr.Current;
					if (!preparedXids.Contains(re.getXid()))
					{
						preparedXids.AddLast(re.getXid());
						int vote = re.getResource().prepare(re.getXid());
						if (vote == XAResource.XA_OK)
						{
							readOnly = false;
						}
						else if (vote == XAResource.XA_RDONLY)
						{
							re.setStatus(RS_READONLY);
						}
						else
						{
						// rollback tx
							status = Status.STATUS_MARKED_ROLLBACK;
							return;
						}
					}
					else
					{
					// set it to readonly, only need to commit once
						re.setStatus(RS_READONLY);
					}
				}
				status = Status.STATUS_PREPARED;
			}
		// commit
			if (!onePhase && readOnly)
			{
				status = Status.STATUS_COMMITTED;
				return;
			}
			if (!onePhase)
			{
				try
				{
					txManager.getTxLog().markAsCommitting(getGlobalId());
				}
				catch (IOException e)
				{
					e.printStackTrace();
					log.severe("Error writing transaction log");
					txManager.setTmNotOk();
					throw new SystemException("TM encountered a problem, " + " error writing transaction log," + e);
				}
			}
			status = Status.STATUS_COMMITTING;
			LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
			while (itr.MoveNext())
			{
				ResourceElement re = itr.Current;
				if (re.getStatus() != RS_READONLY)
				{
					re.getResource().commit(re.getXid(), onePhase);
				}
			}
			status = Status.STATUS_COMMITTED;
		}

//JAVA TO VB & C# CONVERTER WARNING: Method 'throws' clauses are not available in .NET:
//ORIGINAL LINE: void doRollback() throws XAException
		internal virtual void doRollback()
		{
			status = Status.STATUS_ROLLING_BACK;
			LinkedList<Xid> rolledbackXids = new LinkedList<Xid>();
			LinkedList<ResourceElement>.Enumerator itr = resourceList.GetEnumerator();
			while (itr.MoveNext())
			{
				ResourceElement re = itr.Current;
				if (!rolledbackXids.Contains(re.getXid()))
				{
					rolledbackXids.AddLast(re.getXid());
					re.getResource().rollback(re.getXid());
				}
			}
			status = Status.STATUS_ROLLEDBACK;
		}

		private class ResourceElement
		{
			private Xid xid = null;
			private XAResource resource = null;
			private int status;

			internal ResourceElement(Xid xid, XAResource resource)
			{
				this.xid = xid;
				this.resource = resource;
				status = RS_ENLISTED;
			}

			internal virtual Xid getXid()
			{
				return xid;
			}

			internal virtual XAResource getResource()
			{
				return resource;
			}

			internal virtual int getStatus()
			{
				return status;
			}

			internal virtual void setStatus(int status)
			{
				this.status = status;
			}

			public override string ToString()
			{
				string statusString = null;
				switch (status)
				{
					case RS_ENLISTED:
						statusString = "ENLISTED";
						break;
					case RS_DELISTED:
						statusString = "DELISTED";
						break;
					case RS_SUSPENDED:
						statusString = "SUSPENDED";
						break;
					case RS_READONLY:
						statusString = "READONLY";
						break;
					default:
						statusString = "UNKNOWN";
					break;
				}

				return "Xid[" + xid + "] XAResource[" + resource + "] Status[" + statusString + "]";
			}
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void markAsActive()
		{
			if (active)
			{
				throw new IllegalStateException("Transaction[" + this + "] already active");
			}
			active = true;
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		internal virtual void markAsSuspended()
		{
			if (!active)
			{
				throw new IllegalStateException("Transaction[" + this + "] already suspended");
			}
			active = false;
		}
	}
}